
今天我們要來實作 Kimoji App 裡多選標籤的功能。我們會把勾選的狀態和邏輯放到 ViewModel。透過讓 ViewModel 管理所有狀態,可以讓程式碼更簡潔,也更容易測試。

此系列文章是以我的業餘專案: Kimoji 作為範例。
這款以純 Jetpack Compose 撰寫的 side project,已經在 Google Play 上架。 歡迎試玩!
立馬下載
限免兌換碼
Label data model,以便讓它儲存勾選狀態,並把預設值設為 false。data class Label(
    val id: Long, 
    val name: String, 
    var checked: Boolean = false
)
ViewModel 中實作 changeLabelChecked 函式,用這個函式接收要修改的標籤,並用的新值去修改標籤勾選狀態。class DiaryViewModel : ViewModel() {
   ...
   fun changeLabelChecked(label: Label, checked: Boolean) =
       labels.find { it.id == label.id }?.let { label ->
           label.checked = checked
       }
}
DiaryScreen 提供 ViewModel 的 changeLabelChecked 函式給「標籤清單」 composable 的 onCheckedLabel。如下所示:@Composable
fun DiaryScreen(
    modifier: Modifier = Modifier, 
    diaryViewModel: DiaryViewModel = viewModel()
) {
   Column(modifier = modifier) {
       LabelList(
           labels = diaryViewModel.labels,
           onCheckedLabel = { label, checked ->
               diaryViewModel.changeLabelChecked(label, checked)
           }
       )
   }
}
LabelList 並在介面上新增一個 onCheckedLabel lambda 函式參數,以便向下傳遞給 LabelItem
@Composable
fun LabelList(
    modifier: Modifier = Modifier,
    labels: List<Label>,
    onCheckedLabel: (Label, Boolean) -> Unit
) {
    LazyVerticalGrid(
        columns = GridCells.Fixed(3),
        modifier = modifier
    ) {
        items(
            items = labels,
            key = { label -> label.id }
        ) { label ->
            LabelItem(
                name = label.name,
                checked = label.checked,
                onCheckedChange = { checked -> onCheckedLabel(label, checked) }
            )
        }
    }
}
LabelItem.kt 檔案。這個檔案內有個 composable function:會根據 LabelList 提供的 checked state 來決定 borderColor 和 backgroundColor。@Composable
fun LabelItem(
    modifier: Modifier = Modifier,
    name: String,
    checked: Boolean,
    onCheckedChange: (Boolean) -> Unit
) {
    val borderColor = if (checked) {
        MaterialTheme.colorScheme.primary.copy(alpha = 0.5f)
    } else {
        MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f)
    }
    val backgroundColor = if (checked) {
        MaterialTheme.colorScheme.primary.copy(alpha = 0.12f)
    } else {
        MaterialTheme.colorScheme.background
    }
    Surface(
        border = BorderStroke(
            width = 1.dp,
            color = borderColor
        )
    ) {
        Row(
            modifier = modifier
                .background(backgroundColor)
                .clickable(onClick = { onCheckedChange(!checked) }),
            verticalAlignment = Alignment.CenterVertically
        ) {
            Text(
                modifier = Modifier
                    .weight(1f)
                    .padding(start = 16.dp),
                text = name
            )
        }
    }
}

這是因為 Compose 只會觀察 MutableList 有沒有新增及移除 element。所以 Compose 無法發現 item 內的值有變化 (在這裡是 checkedState),除非我們叫 Compose 也要觀察這些內容。
我們將在明天的文章中,繼續探討解決方法。
此系列文章是以我的業餘專案:Kimoji 為範例。
Kimoji 是一款心情日記 App,讓你用可愛的 emoji 來撰寫你的心情日記。現在就來試試這款設計精美的微日記吧!
立馬下載
限免兌換碼
Reference: https://developer.android.com/codelabs/jetpack-compose-state